﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using AAEngine.Engine;
using AAEngine.Engine.Debug;
using AAEngine.Engine.Components;
using AAEngine.Rendering2D.Renderers;

namespace AAEngine.Rendering2D
{
    public class SceneView2D : AnimatedComponent
    {
        private float _minZoom = 0.0f;
        public float MinZoom
        {
            get { return _minZoom; }
            set { _minZoom = value; }
        }

        public float MaxZoom { get; set; }

        /// <summary>
        /// If set, every frame, trackObject's position is read and assigned
        /// to the scene's position, so that the scene follows the trackObject.
        /// </summary>
        public RendererBase TrackObject { get; set; }

        /// <summary>
        /// An x/y offset for adjusting the camera's focus around the tracked
        /// object.
        /// 
        /// Only applies if trackObject is set.
        /// </summary>
        public Vector2 TrackOffset { get; set; }

        public Box? TrackLimitRectangle { get; set; }

        public Box SceneViewBounds
        {
            get
            {
                if (TrackObject != null)
                {
                    Position = new Vector2(
                        -(TrackObject.Position.X + TrackOffset.X),
                        -(TrackObject.Position.Y + TrackOffset.Y));
                }

                if (TrackLimitRectangle != null)
                {
                    Box centeredLimitBounds = new Box(
                        TrackLimitRectangle.Value.X + (ViewArea.Width * 0.5f) / Zoom,
                        TrackLimitRectangle.Value.Y + (ViewArea.Height * 0.5f) / Zoom,
                        TrackLimitRectangle.Value.Width - (ViewArea.Width / Zoom),
                        TrackLimitRectangle.Value.Height - (ViewArea.Height / Zoom));

                    _position.X = MathHelper.Clamp(_position.X, -centeredLimitBounds.Right, -centeredLimitBounds.Left);
                    _position.Y = MathHelper.Clamp(_position.Y, -centeredLimitBounds.Bottom, -centeredLimitBounds.Top);
                }

                UpdateTransform();

                return new Box(
                    -Position.X - ViewArea.Width / Zoom * 0.5f,
                    -Position.Y - ViewArea.Height / Zoom * 0.5f,
                    ViewArea.Width / Zoom,
                    ViewArea.Height / Zoom);
            }
        }

        private Matrix _transform = new Matrix();
        private bool _transformDirty = false;
        private void UpdateTransform()
        {
            if (!_transformDirty)
            {
                return;
            }
            _transformDirty = false;

            _transform = Matrix.CreateTranslation(new Vector3(_position.X, _position.Y, 0)) *
                Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) *
                Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation)) *
                Matrix.CreateTranslation(ViewArea.Width / Zoom * 0.5f, ViewArea.Height / Zoom * 0.5f, 0f);
        }

        public override void OnFrame(float deltaTime)
        {
            if (TrackObject != null)
            {
                Position = new Vector2(
                    -(TrackObject.Position.X + TrackOffset.X),
                    -(TrackObject.Position.Y + TrackOffset.Y));
            }

            if (TrackLimitRectangle != null)
            {
                Box centeredLimitBounds = new Box(
                    TrackLimitRectangle.Value.X + (ViewArea.Width * 0.5f) / Zoom,
                    TrackLimitRectangle.Value.Y + (ViewArea.Height * 0.5f) / Zoom,
                    TrackLimitRectangle.Value.Width - (ViewArea.Width / Zoom),
                    TrackLimitRectangle.Value.Height - (ViewArea.Height / Zoom));

                _position.X = MathHelper.Clamp(_position.X, -centeredLimitBounds.Right, -centeredLimitBounds.Left);
                _position.Y = MathHelper.Clamp(_position.Y, -centeredLimitBounds.Bottom, -centeredLimitBounds.Top);
            }

            UpdateTransform();
        }

        private void ScreenPan(int deltaX, int deltaY)
        {
            if (deltaX == 0 && deltaY == 0)
            {
                return;
            }

            _position.X -= (int)(deltaX / _zoom);
            _position.Y -= (int)(deltaY / _zoom);
            _transformDirty = true;
        }

        private float _rotation;
        public float Rotation 
        {
            get { return _rotation; }
            set 
            {
                if (_rotation != value)
                {
                    _rotation = value;
                    _transformDirty = true;
                }
            }
        }

        private Vector2 _position;
        public Vector2 Position 
        {
            get { return _position; }
            set
            {
                int newX = (int)value.X;
                int newY = (int)value.Y;

                if (_position.X == newX && _position.Y == newY)
                {
                    return;
                }

                _position.X = newX;
                _position.Y = newY;

                if (TrackLimitRectangle != null)
                {
                    Box centeredLimitBounds = new Box(
                        TrackLimitRectangle.Value.X + (ViewArea.Width * 0.5f) / Zoom,
                        TrackLimitRectangle.Value.Y + (ViewArea.Height * 0.5f) / Zoom,
                        TrackLimitRectangle.Value.Width - (ViewArea.Width / Zoom),
                        TrackLimitRectangle.Value.Height - (ViewArea.Height / Zoom));

                    _position.X = MathHelper.Clamp(_position.X, -centeredLimitBounds.Right, -centeredLimitBounds.Left);
                    _position.Y = MathHelper.Clamp(_position.Y, -centeredLimitBounds.Bottom, -centeredLimitBounds.Top);
                }

                _transformDirty = true;
            }
        }

        private float _zoom = 1;
        public float Zoom
        {
            get { return _zoom; }
            set
            {
                value = MathHelper.Clamp(value, MinZoom, MaxZoom);
                if (value == _zoom)
                {
                    return;
                }

                _zoom = value;
                _transformDirty = true;
            }
        }

        public IScene2D Scene { get; set; }

        public bool ClearBeforeRender { get; set; }

        public Color ClearColor { get; set; }

        private RenderQueue _renderQueue = new RenderQueue();
        public RenderQueue RenderQueue
        {
            get { return _renderQueue; }
            set { _renderQueue = value; }
        }

        private Viewport _viewport;
        private Rectangle _viewArea;
        public Rectangle ViewArea
        {
            get
            {
                return _viewArea;
            }
            set
            {
                _viewArea = value;
                _viewport = new Viewport() { X = _viewArea.X, Y = _viewArea.Y, Width = _viewArea.Width, Height = _viewArea.Height };
            }
        }

        public SceneView2D()
        {
            UpdatePriorityNumber = -10;
        }

        public override void OnAdd()
        {
            base.OnAdd();

            _transformDirty = true;
            UpdateTransform();
        }

        public void Render(GraphicsDevice graphicsDevice)
        {
            Profiler.Enter("SceneView2D::Render");

            // Calculate the camera world extents
            Box worldExtents = new Box();

            if (Rotation == 0)
            {
                worldExtents.X = -Position.X - (float)ViewArea.Width / (2f * Zoom);
                worldExtents.Y = -Position.Y - (float)ViewArea.Height / (2f * Zoom);
                worldExtents.Width = (float)ViewArea.Width / Zoom;
                worldExtents.Height = (float)ViewArea.Height / Zoom;
            }
            else
            {
                float sin = (float)Math.Abs(Math.Sin(MathHelper.ToRadians(Rotation)));
                float cos = (float)Math.Abs(Math.Cos(MathHelper.ToRadians(Rotation)));

                Vector2 area = new Vector2(
                    (ViewArea.Width * cos + ViewArea.Height * sin) / (2f * Zoom),
                    (ViewArea.Width * sin + ViewArea.Height * cos) / (2f * Zoom)
                    );

                worldExtents.X = -Position.X - area.X;
                worldExtents.Y = -Position.Y + area.Y;
                worldExtents.Width = area.X * 2;
                worldExtents.Height = area.X * 2;
            }

            Scene.RenderScene(worldExtents, RenderQueue);

            Viewport oldViewport = graphicsDevice.Viewport;
            graphicsDevice.Viewport = _viewport;

            if (ClearBeforeRender)
            {
                graphicsDevice.Clear(ClearColor);
            }

            RenderQueue.Render(graphicsDevice, _transform);

            graphicsDevice.Viewport = oldViewport;

            //SpriteFont font = AAGame.EngineContent.Load<SpriteFont>("Fonts/SystemFont");
            //SpriteBatch sb = new SpriteBatch(graphicsDevice);
            //sb.Begin();
            //sb.DrawString(font, String.Format("CameraLocation: {0}, {1}", Position.X, Position.Y), Vector2.Zero, Color.Black);
            //sb.DrawString(font, String.Format("WorldExtents: {0}, {1}, {2}, {3}", worldExtents.X, worldExtents.Y, worldExtents.Width, worldExtents.Height), new Vector2(0, font.LineSpacing), Color.Black);
            //sb.End();

            Profiler.Exit("SceneView2D::Render");
        }
    }
}
